package ro.flashbite.game_engine.view.sheet
{
	import com.greensock.TweenMax;
	import com.greensock.events.TweenEvent;
	
	import flash.display.Sprite;
	import flash.utils.Dictionary;
	
	import ro.flashbite.interfaces.IInteractive;
	import ro.flashbite.interfaces.ISleepWakeupDestroy;
	import ro.flashbite.loggers.ILogger;
	import ro.flashbite.loggers.LoggerFactory;
	
	/**
	 * Base class that can play a sprite sheet with FrameLabels;
	 *
	 * Uses a xml like this : 
	 * <states>
	 * 	<state id="idle" 	startFrame="1" 	endFrame="20"/>
	 * 	<state id="walk" 	startFrame="21" endFrame="55"/>
	 * 	<state id="run" 	startFrame="56" endFrame="64"/>
	 * 	<state id="air" 	startFrame="65" endFrame="91"/>
	 * </states>
	 *  
	 * v 1.0
	 * 
	 * @author Adrian Barbu
	 * 
	 */	
	public final class FrameLabeledSpriteSheet extends Sprite implements ISleepWakeupDestroy, IInteractive
	{
		/**
		 * sprite sheet used to play 
		 */		
		private var _spriteSheet:	BaseSpriteSheet;
		/**
		 * holds all states of the application 
		 */		
		private var _statesDict	:	Dictionary;
		/**
		 * logger for debugging 
		 */		
		private var _logger		:	ILogger;
		/**
		 * holds data received in playState function 
		 */		
		private var _data		:	DataVO;
		
		// ================================================================================================================================
		// CONSTRUCTOR
		// ================================================================================================================================
		
		public function FrameLabeledSpriteSheet(name:String, spriteSheet:BaseSpriteSheet, xml:XML)
		{
			_constructor(name, spriteSheet, xml);
		}
		private function _constructor(name:String, spriteSheet:BaseSpriteSheet, xml:XML):void
		{
			_spriteSheet = spriteSheet;
			_logger = LoggerFactory.getLogger("Character - " + name);
			
			//read statesXml
			_statesDict = new Dictionary(true);
			var statesXmlList:XMLList = xml.state as XMLList,
				stateXml:XML,
				stateID:String;
			for each (stateXml in statesXmlList) {
				stateID = String(stateXml.@id);
				_statesDict[stateID] = new StateVO(stateID, stateXml.@startFrame, stateXml.@endFrame);
			}
			
			//add spriteSheet to this
			this.addChild(_spriteSheet);
			
			disable();
		}
		
		// ================================================================================================================================
		// ISleepWakeupDestroy
		// ================================================================================================================================
		
		public function sleep():void
		{
			if (_spriteSheet) _spriteSheet.sleep();
		}
		
		public function wakeup():void
		{
			if (_spriteSheet) _spriteSheet.wakeup();
		}
		
		public function destroy():void
		{
			sleep();
			
			if (_spriteSheet) {
				var spriteSheetAnim:TweenMax = _spriteSheet.animation;
				if (spriteSheetAnim) {
					spriteSheetAnim.removeEventListener(TweenEvent.COMPLETE, onInternalAnimTweenEvent);
					spriteSheetAnim.removeEventListener(TweenEvent.REPEAT, onInternalAnimTweenEvent);
				}
				_spriteSheet.destroy();
				_spriteSheet = null;
			}
			
			if (_statesDict) {
				var stateID:String,
					stateVO:StateVO;
				for (stateID in _statesDict) {
					stateVO = _statesDict[stateID];
					stateVO.destroy();
					delete _statesDict[stateID];
				}
				_statesDict = null;
			}
			
			if (_data) {
				_data.destroy();
				_data = null;
			}
			
			if (_logger) {
				LoggerFactory.clearLogger(_logger.name);
				_logger = null;
			}
		}
		
		// ================================================================================================================================
		// CONSTRUCTOR
		// ================================================================================================================================
	
		public function enable():void
		{
			this.mouseEnabled = this.mouseChildren = true;
		}
		
		public function disable():void
		{
			//no mouse interaction on this; but items beneath can be 'accesed'
			this.mouseEnabled = false; this.mouseChildren = true;
		}
		
		// ================================================================================================================================
		// PUBLIC
		// ================================================================================================================================
		
		/**
		 * Plays a state; If the character is in that state, it will continue. If the caracter isn't in that state, it will play current state,
		 * and at the end, it will play given state
		 */		
		public function playState(stateID:String, speed:Number = 1, repeatCount:int = 0, onCompleteFunc:Function = null, onCompleteParams:Array = null):void
		{
			if (_statesDict[stateID]) {
				var currentStateVO:StateVO = getCurrentStateVO();
				if (currentStateVO) {
					//wait for the current state to finish
					var spriteSheetAnim:TweenMax = _spriteSheet.animation;
					if (spriteSheetAnim) {
						//remove any listeners
						spriteSheetAnim.removeEventListener(TweenEvent.COMPLETE, onInternalAnimTweenEvent);
						spriteSheetAnim.removeEventListener(TweenEvent.REPEAT, onInternalAnimTweenEvent);
						if (_spriteSheet.isPlaying) {
							//save data received
							if (_data) {
								_data.stateID = stateID;
								_data.speed = speed;
								_data.repeatCount = repeatCount;
								_data.onCompleteFunc = onCompleteFunc;
								_data.onCompleteParams = onCompleteParams;
							} else {
								_data = new DataVO(stateID, speed, repeatCount, onCompleteFunc, onCompleteParams);
							}
							
							//wait till end
							spriteSheetAnim.addEventListener(TweenEvent.COMPLETE, onInternalAnimTweenEvent, false, 0, true);
							spriteSheetAnim.addEventListener(TweenEvent.REPEAT, onInternalAnimTweenEvent, false, 0, true);
							//_logger.debug("waitForFinish; animation in progress");
						} else {
							//just play the new anim
							//_logger.debug("gotoAndPlay : the SpriteSheet isn't playing");
							gotoAndPlay(stateID, speed, repeatCount, onCompleteFunc, onCompleteParams);
						}
					} else {
						//just play the new anim
						//_logger.debug("gotoAndPlay : the SpriteSheet isn't started");
						gotoAndPlay(stateID, speed, repeatCount, onCompleteFunc, onCompleteParams);
					}
				} else {
					//nothing
					_logger.error("Current stateID : " + currentState + " isn't registered in database");
				}
			} else {
				_logger.error("This stateID : " + stateID + " isn't registered in database");
			}
		}
		
		/**
		 * Plays a state from beggining. Current animation will be stopped
		 */		
		public function gotoAndPlay(stateID:String, speed:Number = 1, repeatCount:int = 0, onCompleteFunc:Function = null, onCompleteParams:Array = null):void
		{
			gotoAndStop(stateID);
			onCompleteReachedState(stateID, speed, repeatCount, onCompleteFunc, onCompleteParams);
		}
		
		/**
		 * Go current animation on the first frame fof the specified stateID
		 */		
		public function gotoAndStop(stateID:String):void
		{
			if (_statesDict[stateID]) {
				_spriteSheet.gotoAndStop((_statesDict[stateID] as StateVO).startFrame);
			} else {
				_logger.error("This stateID : " + stateID + " isn't registered in database");
			}
		}
		
		/**
		 * Stop current animation 
		 */		
		public function stop():void
		{
			_spriteSheet.stop();
		}

		// ================================================================================================================================
		// PRIVATE
		// ================================================================================================================================
		
		private function onInternalAnimTweenEvent(e:TweenEvent):void
		{
			//remove listeners
			var anim:TweenMax = e.currentTarget as TweenMax;
			anim.removeEventListener(TweenEvent.COMPLETE, onInternalAnimTweenEvent);
			anim.removeEventListener(TweenEvent.REPEAT, onInternalAnimTweenEvent);
			//_logger.debug("Complete waitForFinish current state; Now play the new state : " + _data.stateID);
			
			//just play the new state
			onCompleteReachedState(_data.stateID, _data.speed, _data.repeatCount, _data.onCompleteFunc, _data.onCompleteParams);
		}
		
		private function onCompleteReachedState(stateID:String, speed:Number = 1, repeatCount:int = 0, onCompleteFunc:Function = null, onCompleteParams:Array = null):void
		{
			if (_statesDict[stateID]) {
				var stateVO:StateVO = _statesDict[stateID] as StateVO;
				_spriteSheet.playFromTo(stateVO.startFrame, stateVO.endFrame, speed, repeatCount, onCompleteFunc, onCompleteParams);
			} else {
				_logger.error("This stateID : " + stateID + " isn't registered in database");
			}
 		}
		
		private function getCurrentStateVO():StateVO
		{
			var stateVO:StateVO,
				frameCurrent:uint = _spriteSheet.currentFrame,
				stateID:String;
			
			for (stateID in _statesDict) {
				stateVO = _statesDict[stateID] as StateVO;
				if (stateVO.containsFrame(frameCurrent)) {
					return stateVO;
				}
			}
			
			return null;
		}
		
		// ================================================================================================================================
		// GETTERS, SETTERS
		// ================================================================================================================================
		
		public function get currentState():String 
		{ 
			var currentStateVO:StateVO = getCurrentStateVO();
			
			if (currentStateVO) return currentStateVO.id;
			else {
				_logger.error("Couldn't find state on currentFrame = " + _spriteSheet.currentFrame);
				return null;
			}
		}
		
		public function get currentFrame()	: uint 		{ return _spriteSheet.currentFrame; }
		
		public function get isPlaying()		: Boolean 	{ return _spriteSheet.isPlaying; 	}
	}
}

// ================================================================================================================================
// saving data for rendering
// ================================================================================================================================
import ro.flashbite.interfaces.IDestroyable;

class DataVO implements IDestroyable
{
	public var stateID:String;
	public var speed:Number;
	public var repeatCount:int;
	public var onCompleteFunc:Function;
	public var onCompleteParams:Array;
	
	public function DataVO(stateID:String, speed:Number, repeatCount:int, onCompleteFunc:Function, onCompleteParams:Array)
	{
		this.stateID = stateID;
		this.speed = speed;
		this.repeatCount = repeatCount;
		this.onCompleteFunc = onCompleteFunc;
		this.onCompleteParams = onCompleteParams;
	}
	
	public function destroy():void
	{
		this.stateID = null;
		this.onCompleteFunc = null;
		this.onCompleteParams = null;
	}
}

// ================================================================================================================================
// state info
// ================================================================================================================================
import ro.flashbite.interfaces.IDestroyable;

class StateVO implements IDestroyable {
	
	public var id			:	String;
	public var startFrame	:	uint;
	public var endFrame		:	uint;
	
	public function StateVO(id:String, startFrame:uint, endFrame:uint)
	{
		this.id = id;
		this.startFrame = startFrame;
		this.endFrame = endFrame;
	}
	
	public function destroy():void
	{
		this.id = null;
	}
	
	public function containsFrame(frame:uint):Boolean { return ( (frame >= startFrame) && (frame <= endFrame) ); }
}